--[[
===== Syntax Definition =====

[varname], square-brackets, variable
"fixedstring", double-quotes, a fixed string value
..., ellipsis, repeat 0 to N times

===== Data Structure =====

Eva.Lib.DB = {
	connections = {
		"[dbName1]" = [dbController1],
		"[dbName2]" = [dbController2],
		...
	}
}

dbController = {
	dbName = "[dbName]",
	db = [db],

	dbTables = {
		[object1] = "[dbTableName1]",
		[object2] = "[dbTableName2]",
		...
	},

	defaultObjs = {
		[object1] = [defaultTable1],
		[object2] = [defaultTable2],
		...
	}
}

===== Usage Example =====
local config = {...}
local dbController = DB:Open("MyDB")
dbController:Load("config", config, false)
--do some business logic ...
dbController:Save(config)

]]

Eva.Lib.DB = {
	connections = {}
}

local DB = Eva.Lib.DB

local function CheckObjectType(obj, ...)
	local flag = true

	for i = 1, select("#", ...) do
		local tstring = select(i, ...)

		if (type(obj) ~= tstring) then
			flag = false
			break
		end
	end

	assert(flag, "Object wrong type.")
end

local function CheckObjectExist(obj)
	assert(type(obj) == "table", "Object not table type, you need to use a non-nil table object.")
end

local function CheckDBExist(controller)
	assert(controller.db, "DB not exist, you need to use DB.Open to open DB.")
end

local function CheckObjectSavable(controller, obj)
	assert(controller.dbTables[obj], "Object not a savable object, you need to use db.Load to register this object to DB.")
end

local function tablecopy(src, dst)
	if (type(src) == "table" and type(dst) == "table") then
		for k, v in pairs(src) do
			local tmpk = k
			local tmpv = v

			if (type(k) == "table") then
				tmpk = {}
				tablecopy(k, tmpk)
			end

			if (type(v) == "table") then
				tmpv = {}
				tablecopy(v, tmpv)
			end

			dst[tmpk] = tmpv
		end
	end
end

--@notes Open a DB after event VARIABLES_LOADED
--@param [string] dbName DB name from TOC file ## SavedVariables or ## SavedVariablesPerCharacter
--@return [table] controller to control DB
function DB:Open(dbName)
	local db = _G[dbName] or {}
	_G[dbName] = db

	local controller = self.connections[dbName]

	if (not controller) then
		controller = {}
		controller.dbName = dbName
		controller.db = db
		controller.dbTables = {}
		controller.defaultObjs = {}

		setmetatable(controller, self)
		self.__index = self
		self.connections[dbName] = controller
	end

	return controller
end

--@notes Load/Register/Create a business object from DB
--@param [string] tableName
--@param [table] obj
--@param [bool] isAutoSave default is true
--@return [table] obj
function DB:Load(tableName, obj, isAutoSave)
	CheckObjectType(tableName, "string")
	CheckObjectExist(obj)
	CheckDBExist(self)

	if (isAutoSave == nil) then
		isAutoSave = true
	end

	local defaultObj = {}
	tablecopy(obj, defaultObj)
	self.defaultObjs[obj] = defaultObj

	if (isAutoSave) then
		if (self.db[tableName]) then
			tablecopy(self.db[tableName], obj)
			table.wipe(self.db[tableName])
		end

		self.db[tableName] = obj

	else
		if (self.db[tableName]) then
			tablecopy(self.db[tableName], obj)
		end
	end

	self.dbTables[obj] = tableName
	return obj
end

--@notes Save the savable object in current DB
--@param [table] obj The savable object
--@return [nil]
function DB:Save(obj)
	CheckObjectExist(obj)
	CheckDBExist(self)
	CheckObjectSavable(self, obj)

	local tableName = self.dbTables[obj]

	if (obj ~= self.db[tableName]) then
		self.db[tableName] = self.db[tableName] or {}
		table.wipe(self.db[tableName])
		tablecopy(obj, self.db[tableName])
	end
end

--@notes Reset DB and object in default value if set
--@param [table] obj The savable object
--@return [nil]
function DB:Reset(obj)
	CheckObjectExist(obj)
	CheckDBExist(self)

	local defaultObj = self.defaultObjs[obj]
	local tableName = self.dbTables[obj]

	if (self.db[tableName]) then
		table.wipe(self.db[tableName])
		tablecopy(defaultObj, self.db[tableName])
	end
end

--@notes Delete DB
--@param [nil]
--@return [nil]
function DB:Delete()
	self.db = nil
	_G[self.dbName] = nil
end

--@notes Save all tables in current DB
--@param [nil]
--@return [nil]
function DB:SaveAll()
	for obj in pairs(self.dbTables) do
		self:Save(obj)
	end
end

--@notes Reset all tables in current DB
--@param [nil]
--@return [nil]
function DB:ResetAll()
	for obj in pairs(self.dbTables) do
		self:Reset(obj)
	end
end

------------------------
--[[
function test()
	local myframe = CreateFrame("Frame", "myframe")

	--defaultConfig and config has the same data structure,
	--defaultConfig is readonly
	local config = {aaa = 1, bbb = 3, show = false}

	--In Toc ## SavedVariables: MyDB
	--or ## SavedVariablesPerCharacter: MyDB
	local db = DB:Open("MyDB")

	--use in business logic
	--if config in DB then copy it to config object,
	--else copy defaultConfig to config object,
	--bool value use for autosave
	db:Load("config", config)

	--do some business logic
	if (config.show) then
		config.aaa = config.aaa + 1
		myframe:Show()
	end

	--restore config to defaultConfig value, not write in DB
	db:Reset(config, false)

	--do some other business logic ...

	--save to db with config value
	db:Save(config)
end

function test()
	local myframe = CreateFrame("Frame", "myframe")
	local config = {aaa = 1, bbb = 3, show = false}
	local db = DB:Open("MyDB")
	db:Load("config", config)

	--do some business logic

	db:Reset(config, false)

	--do some other business logic ...

	db:Save(config)
end
]]